package server

import (
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"log/slog"
	"net"
	"zenoLink/conf"
	"zenoLink/protocol"
	"zenoLink/protocol/pb"
	"zenoLink/utils"

	"google.golang.org/protobuf/proto"
)

type Socket5Server struct {
	SessionId string
	Host      string
	Port      int32
}

func (m *Socket5Server) Process(client net.Conn) {

	target, err := m.Handshake(client)
	if err != nil {
		fmt.Println("Handshake err=", err)
		client.Close()
		return
	}

	if target == nil {
		fmt.Println("target handshake is empty")
		client.Close()
		return
	}

	m.Forward(client, target)
}

func (m *Socket5Server) CheckUser(sessionId string) bool {

	return true
}

func (m *Socket5Server) Handshake(client net.Conn) (net.Conn, error) {
	data := make([]byte, 2)
	num, err := client.Read(data)
	if err != nil {
		return nil, err
	}
	if num != 2 {
		err = errors.New("read connect data failed")
		return nil, err
	}

	len := binary.BigEndian.Uint16(data[:num])

	if len > conf.MaxLenPackage {
		err = errors.New("connect data is too long")
		slog.Error(fmt.Sprintf("connect data is too long len=%v", len))
		return nil, err
	}

	data = make([]byte, len)
	num, err = io.ReadFull(client, data)
	if err != nil {
		return nil, err
	}

	rawBytes, err := utils.Decrypt(data[:num], conf.EncryptKey)
	if err != nil {

		return nil, err
	}

	req := pb.OutMeshConnet{}
	err = proto.Unmarshal(rawBytes, &req)
	if err != nil {
		slog.Error(fmt.Sprintf("server connect failed , remoteAddr=%v err=%v", client.RemoteAddr().String(), err))
		err = errors.New("connect data failed")
		return nil, err
	}

	m.Host = req.Host
	m.Port = req.Port

	if !m.CheckUser(req.SessionId) {
		err = errors.New("user check failed")
		return nil, err
	}

	destAddrPort := fmt.Sprintf("%s:%d", req.Host, req.Port)
	dest, err := net.Dial("tcp", destAddrPort)
	if err != nil {
		return nil, errors.New("dial dst: " + err.Error())
	}

	res := &pb.OutMeshConnetRes{}
	res.Code = protocol.CodeOk
	res.Enkey = conf.EncryptKey

	resBody, err := proto.Marshal(res)
	if err != nil {
		dest.Close()
		return nil, err
	}

	enBody, err := utils.Encrypt(resBody, conf.EncryptKey)
	if err != nil {
		dest.Close()
		return nil, err
	}

	netData := utils.NetOutPackUint16(enBody)
	_, err = client.Write(netData)
	if err != nil {
		dest.Close()
		return nil, err
	}

	return dest, nil
}

func (m *Socket5Server) Forward(client, target net.Conn) {

	go m.Encode2Raw(client, target)
	go m.Raw2Encode(target, client)

}

func (m *Socket5Server) Raw2Encode(source, targert net.Conn) {

	defer targert.Close()
	defer source.Close()

	// reader := bufio.NewReader(source)
	// writer := bufio.NewWriter(targert)

	buf := make([]byte, 1300)
	for {
		n, err := source.Read(buf)
		if err != nil {
			if err != io.EOF {
				slog.Error("err=", err.Error(), n)
			}

			break
		}

		trans := pb.OutMeshTransfer{}
		trans.SessionId = m.SessionId
		trans.Data = buf[:n]

		bytes, err := proto.Marshal(&trans)
		if err != nil {
			slog.Error(fmt.Sprintf("proto marshal err %v", err))
			break
		}

		enbytes, err := utils.Encrypt(bytes, conf.EncryptKey)
		if err != nil {
			slog.Error(fmt.Sprintf("utils.Encrypt err=%v", err))
			break
		}

		outData := utils.NetOutPackUint16(enbytes)

		// fmt.Println("raw len ", len(trans.Data), " enlength=", len(enbytes), " send len=", len(outData))
		targert.Write(outData)
	}

}

func (m *Socket5Server) Encode2Raw(source, targert net.Conn) {
	defer targert.Close()
	defer source.Close()

	// reader := bufio.NewReader(source)

	buf := make([]byte, 2)
	var n int
	var err error
	for {
		n, err = source.Read(buf)
		if err != nil {
			if err != io.EOF {
				slog.Error("err=", err.Error(), n)
			}

			break
		}
		if n != 2 {
			slog.Error(fmt.Sprintf("read len n is not 2 n=%v", n))
			break
		}

		len := binary.BigEndian.Uint16(buf[:2])
		if len > conf.MaxLenPackage {
			slog.Error("data is too big")
		}
		data := make([]byte, len)
		n, err = source.Read(data)
		if err != nil {
			slog.Error("err=", err.Error())
			break
		}

		deData, err := utils.Decrypt(data[:n], conf.EncryptKey)
		if err != nil {
			slog.Error("err=", err.Error())
			break
		}

		trans := pb.OutMeshTransfer{}
		err = proto.Unmarshal(deData, &trans)
		if err != nil {
			slog.Error(fmt.Sprintf("proto marshal err %v", err))
			break
		}

		targert.Write(trans.Data)
	}

}
